import docker
from docker.types import Resources

# 请将docker_api_url修改为自己创建的Docker Swarm集群主控节点的Docker远程服务URL
docker_api_url = '192.168.0.33:5001' 
# CPU最大不超过1核
cpu_limit = 1*10**9
# 内存最大不超过1GB
mem_limit = 2**30

# 在Docker Swarm中创建一个Jupyter镜像服务
# docker_api_url：集群中主控节点的Docker服务URL。例如：192.168.0.33:5001
# service_name：待创建的服务名称，该名称必须是唯一的。例如：lab-jerry
# image_name：待创建的服务所使用镜像全名称，该镜像是应该在私有注册服务其中存在的。例如：192.168.0.33:5000/kp-base-notebook:1.0
# mount_host_path：宿主机中保存与该用户相关的容器实例数据的目录。例如：/data/jerry
# docker_workspace_path：Jupyter镜像中的用户工作目录。默认是/home/jovyan/work
# docker_port：Jupyter镜像中网站的默认监听端口。默认是8888
def create_service(service_name, image_name, mount_host_path, docker_workspace_path='/home/jovyan/work', docker_port=8888):
    docker_client = docker.DockerClient(base_url=docker_api_url)
    # 指定端口映射
    endpoint_spec = {'Mode':'vip', 'Ports':[{'Protocal':'tcp', 'PublishMode':'ingress', 'TargetPort':docker_port}]}
    # 定义数据目录映射
    mount = '{}:{}:rw'.format(mount_host_path, docker_workspace_path)
    # 定义资源使用限制
    resources = Resources(cpu_limit=cpu_limit, mem_limit=mem_limit)
    try:
        # 创建Service
        service = docker_client.services.create(
            image = image_name,
            endpoint_spec = endpoint_spec,
            mounts = [mount], 
            resources = resources,
            name = service_name)
        return True
    except docker.errors.APIError as ex:
        print(ex)
        return False
    finally:
        docker_client.close()

# 获取指定服务的容器实例所映射到外部的端口号
def get_service_port(service_name):
    docker_client = docker.DockerClient(base_url=docker_api_url)
    try:
        service = docker_client.services.get(service_name)
        # 取tasks中当前状态为running的那一个task，其对应的port为当前container的port
        # 如果在没有关闭servcie的情况下重启了服务器，则重启后该service将自动重建，并且建立一个新的task。
        # 原先的task中的State改为:complete，因此要排除这种情形，只选择running状态的task
        desired_state = 'running'
        running_tasks = service.tasks({'desired-state':desired_state})
        if len(running_tasks) == 0:         # 如果没有正处于运行状态的task，则表明该Service并未运行
            return ("none", 0)
        # Service正在运行，返回其映射到外部的端口号
        port = service.attrs['Endpoint']['Ports'][0]['PublishedPort']
        return (desired_state, port)
    except docker.errors.NotFound as ex:
        print(ex)
        return ("none", 0)
    finally:
        docker_client.close()

# 删除指定的Service
def remove_service(service_name):
    docker_client = docker.DockerClient(base_url=docker_api_url)
    try:
        service = docker_client.services.get(service_name)
        service.remove()
    except docker.errors.NotFound as ex:
        return
    finally:
        docker_client.close()

# 查找名称中包含指定字符串的Service
def find_services(name):
    docker_client = docker.DockerClient(base_url=docker_api_url)
    found_services = []
    try:
        services = docker_client.services.list()
        for service in services:
            service_name = service.attrs['Spec']['Name']
            if service_name.find(name) > 0:
                found_services.append(service_name)            
    finally:
        docker_client.close()
    return found_services
